import assert from "assert";
'use strict';
function Cipher(options) {
    this.options = options;
    this.type = this.options.type;
    this.blockSize = 8;
    this._init();
    this.buffer = new Array(this.blockSize);
    this.bufferOff = 0;
}
Cipher.prototype._init = function _init() {
    // Might be overrided
};
Cipher.prototype.update = function update(data) {
    if (data.length === 0)
        return [];
    if (this.type === 'decrypt')
        return this._updateDecrypt(data);
    else
        return this._updateEncrypt(data);
};
Cipher.prototype._buffer = function _buffer(data, off) {
    // Append data to buffer
    var min = Math.min(this.buffer.length - this.bufferOff, data.length - off);
    for (var i = 0; i < min; i++)
        this.buffer[this.bufferOff + i] = data[off + i];
    this.bufferOff += min;
    // Shift next
    return min;
};
Cipher.prototype._flushBuffer = function _flushBuffer(out, off) {
    this._update(this.buffer, 0, out, off);
    this.bufferOff = 0;
    return this.blockSize;
};
Cipher.prototype._updateEncrypt = function _updateEncrypt(data) {
    var inputOff = 0;
    var outputOff = 0;
    var count = ((this.bufferOff + data.length) / this.blockSize) | 0;
    var out = new Array(count * this.blockSize);
    if (this.bufferOff !== 0) {
        inputOff += this._buffer(data, inputOff);
        if (this.bufferOff === this.buffer.length)
            outputOff += this._flushBuffer(out, outputOff);
    }
    // Write blocks
    var max = data.length - ((data.length - inputOff) % this.blockSize);
    for (; inputOff < max; inputOff += this.blockSize) {
        this._update(data, inputOff, out, outputOff);
        outputOff += this.blockSize;
    }
    // Queue rest
    for (; inputOff < data.length; inputOff++, this.bufferOff++)
        this.buffer[this.bufferOff] = data[inputOff];
    return out;
};
Cipher.prototype._updateDecrypt = function _updateDecrypt(data) {
    var inputOff = 0;
    var outputOff = 0;
    var count = Math.ceil((this.bufferOff + data.length) / this.blockSize) - 1;
    var out = new Array(count * this.blockSize);
    // TODO(indutny): optimize it, this is far from optimal
    for (; count > 0; count--) {
        inputOff += this._buffer(data, inputOff);
        outputOff += this._flushBuffer(out, outputOff);
    }
    // Buffer rest of the input
    inputOff += this._buffer(data, inputOff);
    return out;
};
Cipher.prototype.final = function final(buffer) {
    var first;
    if (buffer)
        first = this.update(buffer);
    var last;
    if (this.type === 'encrypt')
        last = this._finalEncrypt();
    else
        last = this._finalDecrypt();
    if (first)
        return first.concat(last);
    else
        return last;
};
Cipher.prototype._pad = function _pad(buffer, off) {
    if (off === 0)
        return false;
    while (off < buffer.length)
        buffer[off++] = 0;
    return true;
};
Cipher.prototype._finalEncrypt = function _finalEncrypt() {
    if (!this._pad(this.buffer, this.bufferOff))
        return [];
    var out = new Array(this.blockSize);
    this._update(this.buffer, 0, out, 0);
    return out;
};
Cipher.prototype._unpad = function _unpad(buffer) {
    return buffer;
};
Cipher.prototype._finalDecrypt = function _finalDecrypt() {
    assert.equal(this.bufferOff, this.blockSize, 'Not enough data to decrypt');
    var out = new Array(this.blockSize);
    this._flushBuffer(out, 0);
    return this._unpad(out);
};
export default Cipher;
